/*
 * This file is part of Cadmium.
 * Copyright (C) 2007-2010 Xavier Clerc.
 *
 * Cadmium is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Cadmium is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package fr.x9c.cadmium.primitives.graph;

import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.HeadlessException;
import java.awt.image.BufferedImage;

import fr.x9c.cadmium.kernel.Block;
import fr.x9c.cadmium.kernel.CodeRunner;
import fr.x9c.cadmium.kernel.Context;
import fr.x9c.cadmium.kernel.Fail;
import fr.x9c.cadmium.kernel.Primitive;
import fr.x9c.cadmium.kernel.PrimitiveProvider;
import fr.x9c.cadmium.kernel.Value;
import fr.x9c.cadmium.util.Signal;

/**
 * This class provides the implementation of window-related primitives.
 *
 * @author <a href="mailto:cadmium@x9c.fr">Xavier Clerc</a>
 * @version 1.0
 * @since 1.0
 */
@PrimitiveProvider
public final class Open {

    /**
     * Signal identifier for event handling
     * (not used in this implementation).
     */
    private static final Value SIGIO_SIGNAL =
        Value.createFromLong(Signal.Kind.ALRM.getNo());

    /** Main window identifier. */
    private static final String MAIN_WINDOW_ID = "0";

    /**
     * No instance of this class.
     */
    private Open() {
    } // end empty constructor

    /**
     * Initializes the 'graphics' library and opens the window.
     * @param ctxt context
     * @param arg parameters to library, interpreted as: <br/>
     *            <i>&lt;ignored-garbage&gt;&lt;space&gt;&lt;width<tt>x</tt>height&gt;</i>
     * @return <i>unit</i>
     * @throws Fail.Exception if <i>Graphics</i> module has not been initialized
     */
    @Primitive
    public static Value caml_gr_open_graph(final CodeRunner ctxt,
                                           final Value arg)
        throws Fail.Exception {
        final Context c = ctxt.getContext();
        GraphSlot slot = (GraphSlot) c.getSlot(GraphSlot.SLOT);
        if (slot != null) {
            // already initialized
            caml_gr_clear_graph(ctxt, Value.UNIT);
        } else {
            final String[] parts = arg.asBlock().asString().split(" ");
            final String geometry = parts[parts.length - 1];
            String prevTitle = (String) c.getSlot(GraphSlot.SLOT_TITLE);
            if (prevTitle == null) {
                c.registerSlot(GraphSlot.SLOT_TITLE, GraphSlot.DEFAULT_TITLE);
                prevTitle = GraphSlot.DEFAULT_TITLE;
            } // end if
            try {
                slot = new GraphSlot(c.getParameters(), prevTitle, geometry);
            } catch (final HeadlessException he) {
                GraphSlot.fail(ctxt, "Cannot open display", "");
            } // end try/catch
            c.registerSlot(GraphSlot.SLOT, slot);
        } // end if/else
        return Value.UNIT;
    } // end method 'caml_gr_open_graph(CodeRunner, Value)'

    /**
     * Returns the window identifier.
     * @param ctxt context
     * @param unit ignored
     * @return <i>"0"</i>
     * @throws Fail.Exception if <i>Graphics</i> module has not been initialized
     */
    @Primitive
    public static Value caml_gr_window_id(final CodeRunner ctxt,
                                          final Value unit)
        throws Fail.Exception {
        GraphSlot.checkGraph(ctxt);
        return Value.createFromBlock(Block.createString(Open.MAIN_WINDOW_ID));
    } // end method 'caml_gr_window_id(CodeRunner, Value)'

    /**
     * Closes the graph window.
     * @param ctxt context
     * @param unit ignored
     * @return <i>unit</i>
     */
    @Primitive
    public static Value caml_gr_close_graph(final CodeRunner ctxt,
                                            final Value unit) {
        final Context c = ctxt.getContext();
        final GraphSlot slot = (GraphSlot) c.getSlot(GraphSlot.SLOT);
        if (slot != null) {
            final Frame f = slot.getFrame();
            if (f != null) {
                f.setVisible(false);
                f.dispose();
            } // end if
            c.removeSlot(GraphSlot.SLOT);
        } // end if
        return Value.UNIT;
    } // end method 'caml_gr_close_graph(CodeRunner, Value)'

    /**
     * Sets the title of the window.
     * @param ctxt context
     * @param t new title
     * @return <i>unit</i>
     */
    @Primitive
    public static Value caml_gr_set_window_title(final CodeRunner ctxt,
                                                 final Value t) {
        final Context c = ctxt.getContext();
        final String s = t.asBlock().asString();
        c.registerSlot(GraphSlot.SLOT_TITLE, s);
        final GraphSlot slot = (GraphSlot) c.getSlot(GraphSlot.SLOT);
        if (slot != null) {
            slot.getFrame().setTitle(s);
        } // end if
        return Value.UNIT;
    } // end method 'caml_gr_set_window_title(CodeRunner, Value)'

    /**
     * Resizes the window.
     * @param ctxt context
     * @param x new width
     * @param y new height
     * @return <i>unit</i>
     * @throws Fail.Exception if <i>Graphics</i> module has not been initialized
     */
    @Primitive
    public static Value caml_gr_resize_window(final CodeRunner ctxt,
                                              final Value x,
                                              final Value y)
        throws Fail.Exception {
        GraphSlot.checkGraph(ctxt).changeSize(x.asLong(), y.asLong());
        caml_gr_clear_graph(ctxt, Value.UNIT);
        return Value.UNIT;
    } // end method 'caml_gr_resize_window(CodeRunner, Value)'

    /**
     * Clears the window.
     * @param ctxt context
     * @param unit ignored
     * @return <i>unit</i>
     * @throws Fail.Exception if <i>Graphics</i> module has not been initialized
     */
    @Primitive
    public static Value caml_gr_clear_graph(final CodeRunner ctxt,
                                            final Value unit)
        throws Fail.Exception {
        final GraphSlot slot = GraphSlot.checkGraph(ctxt);
        final BufferedImage buffer = slot.getBuffer();
        final int w = buffer.getWidth();
        final int h = buffer.getHeight();
        final Graphics2D win = slot.createWindowGraphics();
        if (win != null) {
            win.setColor(slot.getBackground());
            win.fillRect(0, 0, w, h);
            win.dispose();
            slot.repaint();
        } // end if
        final Graphics2D store = slot.createBackingStoreGraphics();
        if (store != null) {
            store.setColor(slot.getBackground());
            store.fillRect(0, 0, w, h);
            store.dispose();
        } // end if
        return Value.UNIT;
    } // end method 'caml_gr_clear_graph(CodeRunner, Value)'

    /**
     * Returns the width of the graphics area.
     * @param ctxt context
     * @param unit ignored
     * @return the width of the graphics area
     * @throws Fail.Exception if <i>Graphics</i> module has not been initialized
     */
    @Primitive
    public static Value caml_gr_size_x(final CodeRunner ctxt,
                                       final Value unit)
        throws Fail.Exception {
        return Value.createFromLong(GraphSlot.checkGraph(ctxt).getWidth());
    } // end method 'caml_gr_size_x(CodeRunner, Value)'

    /**
     * Returns the height of the graphics area.
     * @param ctxt context
     * @param unit ignored
     * @return the height of the graphics area
     * @throws Fail.Exception if <i>Graphics</i> module has not been initialized
     */
    @Primitive
    public static Value caml_gr_size_y(final CodeRunner ctxt,
                                       final Value unit)
        throws Fail.Exception {
        return Value.createFromLong(GraphSlot.checkGraph(ctxt).getHeight());
    } // end method 'caml_gr_size_y(CodeRunner, Value)'

    /**
     * Sets display mode: when activated drawing are done on the window;
     * otherwise window is not modified.
     * @param ctxt context
     * @param b whether to activate display mode
     * @return <i>unit</i>
     * @throws Fail.Exception if <i>Graphics</i> module has not been initialized
     */
    @Primitive
    public static Value caml_gr_display_mode(final CodeRunner ctxt,
                                             final Value b)
        throws Fail.Exception {
        GraphSlot.checkGraph(ctxt).setDisplayMode(b == Value.TRUE);
        return Value.UNIT;
    } // end method 'caml_gr_display_mode(CodeRunner, Value)'

    /**
     * Sets remember mode: when activated drawing are done on the backing
     * store; otherwise backing store is not modified.
     * @param ctxt context
     * @param b whether to activate remember mode
     * @return <i>unit</i>
     * @throws Fail.Exception if <i>Graphics</i> module has not been initialized
     */
    @Primitive
    public static Value caml_gr_remember_mode(final CodeRunner ctxt,
                                              final Value b)
        throws Fail.Exception {
        GraphSlot.checkGraph(ctxt).setRememberMode(b == Value.TRUE);
        return Value.UNIT;
    } // end method 'caml_gr_remember_mode(CodeRunner, Value)'

    /**
     * Copies the content of the backing store onto the frame.
     * @param ctxt context
     * @param unit ignored
     * @return <i>unit</i>
     * @throws Fail.Exception if <i>Graphics</i> module has not been initialized
     */
    @Primitive
    public static Value caml_gr_synchronize(final CodeRunner ctxt,
                                            final Value unit)
        throws Fail.Exception {
        final GraphSlot slot = GraphSlot.checkGraph(ctxt);
        final Graphics2D g = slot.getBuffer().createGraphics();
        g.drawImage(slot.getBackingStore(), 0, 0, null);
        g.dispose();
        slot.repaint();
        return Value.UNIT;
    } // end method 'caml_gr_synchronize(CodeRunner, Value)'

    /**
     * Returns the signal identifier used for event handling. <br/>
     * Events being enqueued by Java code, this signal identifier is
     * not really used; it should only allow for handler registering.
     * @param ctxt context
     * @param unit ignored
     * @return the signal identifier used for event handling
     */
    @Primitive
    public static Value caml_gr_sigio_signal(final CodeRunner ctxt,
                                             final Value unit) {
        return Open.SIGIO_SIGNAL;
    } // end method 'caml_gr_sigio_signal(CodeRunner, Value)'

    /**
     * Does nothing. <br/>
     * Events are enqued by Java code; event listeners being registered in
     * {@link fr.x9c.cadmium.primitives.graph.GraphSlot} constructor.
     * @param ctxt context
     * @param unit ignored
     * @return <i>unit</i>
     */
    @Primitive
    public static Value caml_gr_sigio_handler(final CodeRunner ctxt,
                                              final Value unit) {
        return Value.UNIT;
    } // end method 'caml_gr_sigio_handler(CodeRunner, Value)'

} // end class 'Open'
